Skip to content

feat(flags): add official PostHog OpenFeature provider#695

Open
gustavohstrassburger wants to merge 5 commits into
mainfrom
posthog-code/openfeature-provider
Open

feat(flags): add official PostHog OpenFeature provider#695
gustavohstrassburger wants to merge 5 commits into
mainfrom
posthog-code/openfeature-provider

Conversation

@gustavohstrassburger

Copy link
Copy Markdown
Contributor

What

Adds an official PostHog provider for the OpenFeature Python SDK, built in this repo and shipped as a separate distribution (openfeature-provider-posthog) under the OpenFeature namespace package openfeature.contrib.provider.posthog.

It wraps a configured posthog.Posthog client and resolves all five OpenFeature flag types via the modern, non-deprecated get_feature_flag_result (one call → value + variant + payload + reason):

OpenFeature method Resolves to
get_boolean_value flag enabled
get_string_value multivariate variant key
get_integer_value / get_float_value variant parsed as a number
get_object_value flag's JSON payload (full object support)

Evaluation context maps targeting_keydistinct_id, reserved attributes groups / group_properties → PostHog groups, and every other attribute → person_properties.

Behavior decisions (recommended defaults, easy to change)

  • Missing targeting_keyTargetingKeyMissingError (OpenFeature-idiomatic; SDK returns the caller's default). Opt into anonymous eval via default_distinct_id="anonymous".
  • Type mismatch (e.g. get_string_value on a boolean flag, non-numeric variant for int, non-object payload) → TypeMismatchError → default returned, per spec.
  • send_feature_flag_events=True by default (keeps $feature_flag_called / experiments working); toggleable.
  • Build backend setuptools (repo-consistent); posthog>=6.0.0 floor.

Layout

openfeature-provider/
├── pyproject.toml                # dist: openfeature-provider-posthog (MIT, py>=3.10)
├── README.md / LICENSE / .gitignore / uv.lock
├── openfeature/contrib/provider/posthog/   # PEP 420 namespace; __init__ only at leaf
│   ├── __init__.py  ├── provider.py  └── py.typed
└── tests/            # 20 unit + end-to-end tests (mocked client)

Repo wiring

  • CI: new openfeature-provider job mirroring django5-integrationuv sync, pytest, ruff format/check, mypy, then uv build + twine check in the sub-project's own env.
  • mypy.ini: excludes openfeature-provider/.* from the root mypy pass (the namespace tree is type-checked in its own env where openfeature-sdk is installed).
  • README: link to the provider.

The new top-level openfeature/ tree is isolated from the posthog wheel (explicit packages list) and from the posthog.*-scoped public-API snapshot — verified.

Verification

  • ✅ 20/20 tests pass
  • ✅ ruff format + lint clean under both root (0.12.2) and sub-project (0.15.17)
  • ✅ mypy clean (sub-project env + full root run, 127 files)
  • uv build + twine check pass; wheel ships only the leaf package + py.typed, no clobbering openfeature/__init__.py, no tests
  • ✅ no repo-wide regression: posthog wheel/sdist contain zero openfeature/ entries; public_api_check snapshot up to date

Follow-ups (not in this PR)

  • Confirm the PyPI distribution name openfeature-provider-posthog before first publish. A third-party posthog-openfeature-provider-python (MPL-2.0, deprecated API, no object support) already exists on PyPI; this one is official, MIT, and uses the modern API with full object/JSON support.
  • Publish workflow: the repo's release flow only ships posthog/posthoganalytics; this dist needs its own version/tag + PyPI publish job. No posthoganalytics twin needed.

🤖 Generated with Claude Code

Adds an official PostHog provider for the OpenFeature Python SDK, shipped as a
separate distribution (`openfeature-provider-posthog`) under the OpenFeature
namespace package `openfeature.contrib.provider.posthog`, living in this repo
alongside the SDK.

The provider wraps a configured `posthog.Posthog` client and resolves all five
OpenFeature flag types via the modern, non-deprecated `get_feature_flag_result`
(one call yields value + variant + payload + reason):

- boolean -> `enabled`
- string  -> the multivariate variant key
- int/float -> the variant parsed as a number
- object  -> the flag's JSON payload (full object/JSON support)

Evaluation context maps `targeting_key` -> `distinct_id`, reserved attributes
`groups`/`group_properties` -> PostHog groups, and all other attributes ->
`person_properties`. A missing targeting key raises `TargetingKeyMissingError`
unless `default_distinct_id` is set; type mismatches raise `TypeMismatchError`
so the OpenFeature client returns the caller's default per spec.

Repo wiring:
- New `openfeature-provider` CI job (mirrors the django5 integration job):
  uv sync, pytest, ruff, mypy, build + twine check in the sub-project's own env.
- `mypy.ini`: exclude `openfeature-provider/.*` from the root mypy pass (the
  namespace tree is type-checked in its own env where openfeature-sdk is present).
- README link to the provider.

The new top-level `openfeature/` tree is isolated from the `posthog` build
(explicit packages list) and from the `posthog.*`-scoped public-API snapshot.

Generated-By: PostHog Code
Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "Add official PostHog OpenFeature provide..." | Re-trigger Greptile

Comment thread openfeature-provider/openfeature/contrib/provider/posthog/provider.py Outdated
Comment thread openfeature-provider/tests/test_provider_unit.py Outdated
Comment thread openfeature-provider/openfeature/contrib/provider/posthog/provider.py Outdated
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

posthog-python Compliance Report

Date: 2026-06-26 21:25:23 UTC
Duration: 530105ms

✅ All Tests Passed!

45/45 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 516ms
Format Validation.Event Has Uuid 10007ms
Format Validation.Event Has Lib Properties 10007ms
Format Validation.Distinct Id Is String 10007ms
Format Validation.Token Is Present 10007ms
Format Validation.Custom Properties Preserved 10006ms
Format Validation.Event Has Timestamp 10007ms
Retry Behavior.Retries On 503 18018ms
Retry Behavior.Does Not Retry On 400 12004ms
Retry Behavior.Does Not Retry On 401 10006ms
Retry Behavior.Respects Retry After Header 16012ms
Retry Behavior.Implements Backoff 30018ms
Retry Behavior.Retries On 500 13009ms
Retry Behavior.Retries On 502 16010ms
Retry Behavior.Retries On 504 16011ms
Retry Behavior.Max Retries Respected 30017ms
Deduplication.Generates Unique Uuids 7001ms
Deduplication.Preserves Uuid On Retry 16015ms
Deduplication.Preserves Uuid And Timestamp On Retry 23018ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 16005ms
Deduplication.No Duplicate Events In Batch 10001ms
Deduplication.Different Events Have Different Uuids 10007ms
Compression.Sends Gzip When Enabled 10006ms
Batch Format.Uses Proper Batch Structure 10007ms
Batch Format.Flush With No Events Sends Nothing 5005ms
Batch Format.Multiple Events Batched Together 10005ms
Error Handling.Does Not Retry On 403 12008ms
Error Handling.Does Not Retry On 413 10006ms
Error Handling.Retries On 408 14013ms

Feature_Flags Tests

16/16 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 9502ms
Request Payload.Flags Request Uses V2 Query Param 10006ms
Request Payload.Flags Request Hits Flags Path Not Decide 10007ms
Request Payload.Flags Request Omits Authorization Header 10006ms
Request Payload.Token In Flags Body Matches Init 10007ms
Request Payload.Groups Round Trip 10006ms
Request Payload.Groups Default To Empty Object 10006ms
Request Payload.Person Properties Distinct Id Auto Populated When Caller Omits It 10007ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 10007ms
Request Payload.Disable Geoip Omitted Defaults To False 10006ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 10007ms
Request Lifecycle.No Flags Request On Init Alone 5003ms
Request Lifecycle.No Flags Request On Normal Capture 10507ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 9510ms
Request Lifecycle.Mock Response Value Is Returned To Caller 10003ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 10508ms

- _map_reason: a disabled (enabled=False) result now maps to Reason.DEFAULT
  (the flag is active but no targeting condition matched), reserving
  Reason.DISABLED for when the reason text says the flag itself is off.
  PostHog returns None (-> FlagNotFoundError) for archived/missing flags.
- initialize: log a WARNING (with exc_info) instead of silently swallowing a
  load_feature_flags() failure, so a misconfigured personal_api_key / host /
  permissions is visible while still falling back to remote evaluation.
- tests: collapse the boolean reason-mapping and number-parsing cases into
  @pytest.mark.parametrize, and add coverage for the initialize logging paths.

Generated-By: PostHog Code
Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
@gustavohstrassburger gustavohstrassburger changed the title Add official PostHog OpenFeature provider feat: add official PostHog OpenFeature provider Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Tested this locally end-to-end against a running PostHog instance (not just the mocked unit/e2e tests) — registered the provider with the OpenFeature SDK and evaluated a real boolean flag through client.get_boolean_details(...), and it worked. 🎉

Repro script I used:

"""
Example demonstrating how to use the PostHog OpenFeature provider.

This example shows:
1. Initializing the PostHog client
2. Registering the PostHogProvider with the OpenFeature SDK
3. Evaluating a boolean feature flag via the OpenFeature API
"""

import os

import posthog
from openfeature import api
from openfeature.evaluation_context import EvaluationContext
from openfeature.contrib.provider.posthog import PostHogProvider

# Initialize the PostHog client
posthog_client = posthog.Posthog(
    project_api_key=os.getenv("POSTHOG_API_KEY", "phc_PIPWvLdxL4N9RUvpyENExMOFEz2jXuk5ehmyXFG3A2k"),
    host=os.getenv("POSTHOG_HOST", "http://localhost:8010"),
)

# Register the PostHog provider with OpenFeature
provider = PostHogProvider(posthog_client)
api.set_provider(provider)

# Get the OpenFeature client
client = api.get_client()

# The flag key to evaluate
flag_key = os.getenv("POSTHOG_FLAG_KEY", "my-flag")

# Evaluation context: targeting_key maps to PostHog's distinct_id
ctx = EvaluationContext(targeting_key="test-user")

# Evaluate the boolean flag
result = client.get_boolean_details(flag_key, False, ctx)

print(f"Flag: {flag_key}")
print(f"Value: {result.value}")
print(f"Reason: {result.reason}")
if result.error_message:
    print(f"Error: {result.error_message}")

posthog_client.shutdown()

(The phc_ key above is a local-dev project key pointing at localhost:8010.) Confirms the full path works as installed: namespace import, provider registration, targeting_keydistinct_id mapping, and the boolean resolution returning the expected value/reason.

@gustavohstrassburger gustavohstrassburger requested a review from a team June 25, 2026 20:30
@posthog-project-board-bot posthog-project-board-bot Bot moved this to In Progress in Feature Flags Jun 25, 2026
@gustavohstrassburger gustavohstrassburger marked this pull request as ready for review June 25, 2026 20:33
@gustavohstrassburger gustavohstrassburger requested a review from a team as a code owner June 25, 2026 20:33
@posthog-project-board-bot posthog-project-board-bot Bot moved this from In Progress to In Review in Feature Flags Jun 25, 2026
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Reviews (2): Last reviewed commit: "Address review: add missing tests and op..." | Re-trigger Greptile

@haacked haacked left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-tested provider, nothing blocking. A few non-blocking suggestions inline, the correctness one (TYPE_MISMATCH on unmatched experiments) is the one worth a decision.

I'll leave approval to the client libraries team who may want a look.

evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[str]:
result = self._resolve(flag_key, evaluation_context)
if result.variant is None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Unenrolled users get TYPE_MISMATCH errors when they shouldn't. When a user matches no condition on a multivariate flag, PostHog returns enabled=False, variant=None, so the result.variant is None check here raises TypeMismatchError. The OpenFeature SDK then returns the correct default value but marks it with error_code=TYPE_MISMATCH and reason=ERROR, so normal non-enrollment gets flagged as type-mismatch errors.

You can tell the two cases apart by enabled: when enabled=True, variant=None, it's a genuine mismatch (boolean flag read as string), while enabled=False, variant=None means the flag is off or nobody matched. Return the default with a non-error reason in that case:

        result = self._resolve(flag_key, evaluation_context)
        if result.variant is None:
            if not result.enabled:
                return FlagResolutionDetails(
                    value=default_value,
                    reason=self._map_reason(result),
                    flag_metadata=self._flag_metadata(result),
                )
            raise TypeMismatchError(
                f"Flag '{flag_key}' has no string variant (boolean flag)."
            )

The same variant is None branch is in _resolve_number; keep the non-numeric-variant case there raising TypeMismatchError. One tradeoff: a disabled boolean flag read as a string would reclassify from TYPE_MISMATCH to DISABLED, since enabled=False, variant=None can't tell the two apart.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8f24613. enabled=False, variant=None now returns the default with a normal reason (DEFAULT/DISABLED) instead of raising TypeMismatchError; the genuine boolean-read-as-string case (enabled=True, variant=None) still raises. Applied the same in _resolve_number, and (per Manoel) in resolve_object_details. Added detail tests asserting error_code is None on the non-enrollment path.

groups = attrs.get(GROUPS_KEY) or {}
group_properties = attrs.get(GROUP_PROPERTIES_KEY) or {}
person_properties = {k: v for k, v in attrs.items() if k not in _RESERVED_KEYS}
groups = groups if isinstance(groups, dict) else {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Nothing tests the non-dict fallback here. test_context_split only passes well-formed dicts, so when a caller sets groups to a string or list, the isinstance(groups, dict) guard that coerces it to {} is unverified. Drop that guard in a later refactor and a non-dict would flow straight into get_feature_flag_result with no test to catch it.

Add a case mirroring test_context_split that passes groups="acme" and asserts the forwarded kwargs["groups"] is None.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8f24613. Added test_context_split_non_dict_groups_coerced_to_none, parametrized over "acme", a list, and an int, asserting the forwarded groups/group_properties kwargs are None.

Comment thread .github/workflows/ci.yml
- name: Set up Python 3.12
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55
with:
python-version: 3.12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The package declares support for Python 3.10 through 3.14 (requires-python plus classifiers in pyproject.toml), but this job only tests on 3.12. mypy targets 3.10, so type errors are caught, but nothing exercises the floor or ceiling at runtime. A 3.10-only incompatibility or a 3.14 behavior change would ship unverified.

The tests and import-check jobs already matrix across all five versions.

        strategy:
            matrix:
                python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
        steps:
            ...
            - name: Set up Python ${{ matrix.python-version }}
              uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55
              with:
                  python-version: ${{ matrix.python-version }}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree the matrix should mirror the tests/import-check jobs (3.10–3.14). I do not edit .github/workflows/** in this automated review-fix turn (guardrail), so I have left this for a maintainer to apply directly.

evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[bool]:
result = self._resolve(flag_key, evaluation_context)
return FlagResolutionDetails(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The four typed resolvers end with the same FlagResolutionDetails tail except for value. A small helper keeps the reason/flag_metadata wiring in one place:

    def _details(self, value: _T, result: FeatureFlagResult) -> FlagResolutionDetails[_T]:
        return FlagResolutionDetails(
            value=value,
            variant=result.variant,
            reason=self._map_reason(result),
            flag_metadata=self._flag_metadata(result),
        )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 8f24613 — extracted _details(value, result) and routed all four typed resolvers (and the default-return paths) through it.

@haacked haacked changed the title feat: add official PostHog OpenFeature provider feat(flags): add official PostHog OpenFeature provider Jun 26, 2026
@@ -0,0 +1,103 @@
# PostHog provider for OpenFeature (Python)

@marandaneto marandaneto Jun 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd move all install/snippets to https://posthog.com/docs/feature-flags and just point to the docs
single source of truth for docs, readme gets outdated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 8f24613 — slimmed the README to a minimal quickstart and pointed to https://posthog.com/docs/feature-flags as the single source of truth. Kept only the OpenFeature-specific registration snippet + context mapping (not yet on the docs site); those can move there once the provider is documented.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done earlier in 8f24613 — README slimmed to a minimal quickstart pointing at https://posthog.com/docs/feature-flags as the source of truth.

@marandaneto

Copy link
Copy Markdown
Member

since its a new distribution, you'd need to adapt https://github.com/PostHog/posthog-python/blob/main/.github/workflows/release.yml to publish multiple packages
you can check ruby/rails, the js monorepo, etc, since they also publish multiple packages within the release process

Comment thread openfeature-provider/pyproject.toml Outdated

[project]
name = "openfeature-provider-posthog"
version = "0.1.0"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs to set up sampo here, see other comment related to the release process

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The release wiring (Sampo for the new package + adapting release.yml to publish a second distribution) is a .github/workflows/** + release-config change I will not make in this automated turn. Flagged for a maintainer in a summary comment on the PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in 5236b4b. Sampo already auto-discovers the package as pypi/openfeature-provider-posthog (it has its own pyproject with name+version), and release.yml now builds/publishes/tags it — gated so it only fires when a changeset bumps the provider, leaving posthog-only releases untouched. Added openfeature-provider/CHANGELOG.md for Sampo to maintain. First publish still needs a one-time PyPI trusted-publisher registration for the new project.

@marandaneto

Copy link
Copy Markdown
Member

Optional nice-to-have: add a CI smoke test that installs the built wheel into a fresh env and imports openfeature.contrib.provider.posthog, since namespace packaging issues are easy to miss with source-tree tests.

@marandaneto

Copy link
Copy Markdown
Member

The unmatched-user/default behavior should include object flags as well?
- Existing comment covers string/number variant is None.
- resolve_object_details has the same issue when enabled=False and payload=None: it raises TypeMismatchError, so
OpenFeature reports Reason.ERROR / TYPE_MISMATCH instead of a normal default.
- See openfeature-provider/openfeature/contrib/provider/posthog/provider.py:196.
- If they fix non-enrollment handling, apply it consistently to string/number/object and add detail tests checking
no error_code.

@marandaneto

Copy link
Copy Markdown
Member

left a bunch of comments, main blocker is the release process and a few other comments/suggestions, Phil added good points as well

gustavohstrassburger added a commit to PostHog/posthog-js that referenced this pull request Jun 26, 2026
Adds `@posthog/openfeature-provider`, a JS port of the Python OpenFeature
provider (PostHog/posthog-python#695).

OpenFeature ships two SDKs with incompatible Provider contracts, so this
package ships one provider for each, sharing a runtime-agnostic mapping core:

- `/server` — `PostHogServerProvider` wraps `posthog-node` against
  `@openfeature/server-sdk` (async, multi-user, distinct id from `targetingKey`).
- `/web` — `PostHogWebProvider` wraps `posthog-js` against
  `@openfeature/web-sdk` (synchronous, single-user, context reconciled via
  `onContextChange`).

Both resolve through `getFeatureFlagResult`: boolean→enabled, string→variant,
number→parsed variant, object→payload; missing flag→FLAG_NOT_FOUND,
wrong type→TYPE_MISMATCH. The SDK/client deps are optional peers so a
node-only or browser-only app never installs the other paradigm's stack.

Generated-By: PostHog Code
Task-Id: 46a3f8c9-fbcd-460e-9457-fca583955e5a
Addresses review feedback from @haacked and @marandaneto.

- Correctness (haacked + marandaneto): a user who matches no condition or a
  disabled flag (enabled=False, with no variant/object payload) is no longer
  reported as a TYPE_MISMATCH error. The string/integer/float/object resolvers
  now return the caller's default with a normal reason (DEFAULT/DISABLED) in that
  case, and only raise TypeMismatchError for a genuine mismatch (enabled=True but
  the value can't be coerced to the requested type). Applied consistently across
  string, number, and object.
- Refactor (haacked): extract a shared `_details()` helper for the repeated
  reason/flag_metadata wiring across the typed resolvers.
- Tests (haacked): add non-dict `groups`/`group_properties` coercion cases
  asserting they are forwarded as None; add detail tests asserting unmatched
  string/number/object resolution returns the default with no error_code.
- Docs (marandaneto): slim the README to a minimal quickstart and point to
  https://posthog.com/docs/feature-flags as the single source of truth.

Generated-By: PostHog Code
Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
Implements the remaining review suggestions from @marandaneto.

- Release (release.yml): publish `openfeature-provider-posthog` as a second
  distribution. Sampo already auto-discovers the package (it has its own
  pyproject), so the new steps only build/publish/tag the provider when this
  release actually bumped its version (a changeset targeting
  `pypi/openfeature-provider-posthog`). posthog-only releases skip them, and they
  run after the posthog publish/tag/release so they can never block the core
  release. Uses the same PyPI OIDC trusted-publishing action as
  posthog/posthoganalytics (a trusted publisher for the new project must be
  registered before the first provider release).
- Sampo: add openfeature-provider/CHANGELOG.md for Sampo to maintain.
- CI (ci.yml): add a clean-env smoke test that installs the built wheel and
  imports openfeature.contrib.provider.posthog, catching namespace-packaging
  regressions that source-tree tests miss.
- pyproject: pin posthog to the tested major (>=7.0.0,<8.0.0); the local-branch
  build via [tool.uv.sources] was already in place.

Generated-By: PostHog Code
Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32

Copy link
Copy Markdown
Contributor Author

@marandaneto — implemented your remaining suggestions in 5236b4b:

  • Release (release.yml) now publishes openfeature-provider-posthog as a second distribution. Sampo already auto-discovers it as pypi/openfeature-provider-posthog (own pyproject with name + version), and short_tags = "posthog" means it tags as openfeature-provider-posthog-v{version}. The new build/publish/tag steps are gated — they only run when this release actually bumped the provider (a changeset targeting it), so posthog-only releases skip them, and they run after the posthog publish/tag/release so a provider issue can never block the core release.
  • Sampo: added openfeature-provider/CHANGELOG.md for it to maintain.
  • CI smoke test: the provider job now installs the built wheel into a clean env and imports openfeature.contrib.provider.posthog — catches namespace-packaging regressions source-tree tests miss.
  • Pin: posthog>=7.0.0,<8.0.0 (tested major); local-branch build via [tool.uv.sources] was already in place.

One manual prerequisite for the first provider release (can't be done in a PR): register a PyPI trusted publisher for openfeature-provider-posthog pointing at release.yml + the Release environment, exactly like posthog/posthoganalytics. After that, adding a changeset that bumps the provider triggers its first publish.

Verified locally: 37 tests, ruff, mypy, uv build + clean-env wheel install/import all pass; both workflow YAMLs parse. The release flow itself can only be fully exercised on a real release run.

@marandaneto

Copy link
Copy Markdown
Member

@gustavohstrassburger

Still not fixed / still worth commenting:

  1. Provider release/versioning is still broken

    • Release workflow assumes Sampo can process changesets for pypi/openfeature-provider-posthog.
    • But Sampo does not know that package. I verified in a temp worktree:
      sampo add -p pypi/openfeature-provider-posthog -b patch -m test
      # Failed: Package 'pypi/openfeature-provider-posthog' not found in the workspace.

    So openfeature-provider/pyproject.toml won’t be bumped by Sampo, and the release workflow’s detection at
    .github/workflows/release.yml:245 likely never triggers for this package.

  2. Provider CI is still only Python 3.12

    • openfeature-provider/pyproject.toml claims Python 3.10–3.14 support.
    • .github/workflows/ci.yml:268-271 still only sets up 3.12.

those are still open, the 2. is optional but i think its cool to test across all major versions like we do for the main package

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

3 participants